package pa2;

import java.rmi.RemoteException;
import java.util.HashMap;
import java.util.Timer;
import java.util.TimerTask;

import pa2.node.Node;
import pa2.node.NodeData;
import pa2.system.Peer;
import pa2.system.SystemManagerImpl;
import pa2.util.Callback;
import pa2.util.CompInteger;
import pa2.util.EqualityCompare;


public class CustomSystemManager extends SystemManagerImpl {

	/**
	 * 
	 */
	private static final long serialVersionUID = -348850750957851122L;
	public static final int RANGE = 65535;
	public Node<CompInteger, String, HashMap<CompInteger, String>> rootNode;
	public int currentRootIndex = 0;
	private Timer swapRootTimer;
	private int nrRequests;
	private static long SWAP_ROOT_INTERVAL = 100;
	
	public CustomSystemManager() throws RemoteException {
		super();
	}

	protected synchronized void peersChanged() throws RemoteException {
		//TODO remove peer if remote object invocation fails
		if (peers.size() == 4) {
			System.out.println("***STARTING TO BUILD THE TREE***");
			CompInteger comp;
			Node<CompInteger, String, HashMap<CompInteger, String>> child;

			
			comp =  new CompInteger(RANGE/2);
			rootNode = peers.get(currentRootIndex).createNode(comp, null);

			comp =  new CompInteger(RANGE/4);
			child = peers.get(1).createNode(comp, null);
			rootNode.addChild(child);

			setupTree(0, 8, peers.get(0), (int) Math.pow(2, 6), RANGE, child);
			setupTree(1, 8, peers.get(1), (int) Math.pow(2, 6), RANGE, child);
			
			comp =  new CompInteger(3*RANGE/4);
			child = peers.get(2).createNode(comp, null);
			rootNode.addChild(child);

			setupTree(2, 8, peers.get(2), (int) Math.pow(2, 6), RANGE, child);
			setupTree(3, 8, peers.get(3), (int) Math.pow(2, 6), RANGE, child);
			
			System.out.println("***FINISHED BUILDING THE TREE***");
			//resetSwapRootTimer();

		}
	}
	

	private void setupTree(int currentIndex, int denominator, 
			Peer peer, int maxDenominator, long range, Node<CompInteger, String, HashMap<CompInteger, String>> node) throws RemoteException {
		CompInteger comp;
		
		comp =  new CompInteger((int) ((2*currentIndex+1)*range/denominator));
		if (denominator != maxDenominator) {
			Node<CompInteger, String, HashMap<CompInteger, String>> childNode = peer.createNode(comp, null);
			node.addChild(childNode);
			setupTree(2*currentIndex, denominator*2, peer, maxDenominator, range, node);
			
			setupTree(2*currentIndex+1, denominator*2, peer, maxDenominator, range, node);
		} else {
			HashMap<CompInteger, String> map = new HashMap<CompInteger,String>();
			
			NodeData<CompInteger, String, HashMap<CompInteger, String>> data = 
				new NodeData<CompInteger, String, HashMap<CompInteger,String>>(map);
			
			node.addChild(peer.createNode(comp, data));
		}
	}
	
	
	class SwapRootNode /* extends TimerTask */ {
		
		private CustomSystemManager context;
		private int nrPeers;
		
		public SwapRootNode(CustomSystemManager context, int nrPeers){
			this.context = context;
			this.nrPeers = nrPeers;
		}
		
		//@Override
		public void run() {
			//System.out.print("Changed root node: " + context.currentRootIndex + " ->");
			context.currentRootIndex = (context.currentRootIndex + 1) % nrPeers;
			//System.out.println(context.currentRootIndex);
			try {
				Node<CompInteger, String, HashMap<CompInteger, String>> newRoot = 
					context.getPeers().get(context.currentRootIndex).createNode(new CompInteger(RANGE/2), null);
				newRoot.addChild(context.rootNode.getLeftChild());
				newRoot.addChild(context.rootNode.getRightChild());
				synchronized(context.rootNode){
					context.rootNode = newRoot;
				}
				//context.resetSwapRootTimer();
			} catch (RemoteException e) {
				e.printStackTrace();
			}
			
		}
		
	}

	/*
	public void resetSwapRootTimer() {
		this.swapRootTimer = new Timer();
		this.swapRootTimer.schedule(new SwapRootNode(this, this.peers.size()), 
				CustomSystemManager.SWAP_ROOT_INTERVAL);
	}*/

	@Override
	public void get(CompInteger key, Callback callback) throws RemoteException {
		synchronized(rootNode){
			rootNode.get(key, callback);
			nrRequests++;
		}
		
		if(nrRequests == CustomSystemManager.SWAP_ROOT_INTERVAL){
			SwapRootNode srn = new SwapRootNode(this, this.peers.size());
			srn.run();
			nrRequests = 0;
		}
	}

	@Override
	public void remove(CompInteger key, Callback callback)
			throws RemoteException {
		synchronized(rootNode){
			rootNode.remove(key, callback);
			nrRequests++;
		}
		
		if(nrRequests == CustomSystemManager.SWAP_ROOT_INTERVAL){
			SwapRootNode srn = new SwapRootNode(this, this.peers.size());
			srn.run();
			nrRequests = 0;
		}
	}

	@Override
	public void put(CompInteger key, String value, Callback callback)
			throws RemoteException {
		synchronized(rootNode){
			rootNode.put(key, value, callback);
			nrRequests++;
		}		
		if(nrRequests == CustomSystemManager.SWAP_ROOT_INTERVAL){
			SwapRootNode srn = new SwapRootNode(this, this.peers.size());
			srn.run();
			nrRequests = 0;
		}
	}


}
